# Copyright 2013-present Barefoot Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Thrift PD interface basic tests
"""
from time import sleep
import unittest
import random

import pd_base_tests

from ptf import config
from ptf.testutils import *
from ptf.thriftutils import *

from knet_mgr_test.p4_pd_rpc.ttypes import *
from knet_mgr_pd_rpc.ttypes import *
from pkt_pd_rpc.ttypes import *
from mc_pd_rpc.ttypes import *
from res_pd_rpc.ttypes import *
from devport_mgr_pd_rpc.ttypes import *


swports = [x for x in range(65)]
dev_id = 0


class Mask(Packet):
    name = "Filter Mask"
    fields_desc = [
        BitField("dest_mac", 0, 48),
        BitField("src_mac", 0, 48),
        XShortField("outer_ether_type", 0),
        BitField("packet_type", 0, 3),
        BitField("header_version", 0, 2),
        BitField("packet_version", 0, 2),
        BitField("pad1", 0, 1),
        BitField("fabric_color", 0, 3),
        BitField("fabric_qos", 0, 5),
        XByteField("dst_device", 0),
        XShortField("dst_port_or_group", 0),
        BitField("egress_queue", 0, 5), BitField("tx_bypass", 0, 1),
        BitField("reserved1", 0, 2), XShortField("ingress_port", 0),
        XShortField("ingress_ifindex", 0), XShortField("ingress_bd", 0),
        XShortField("reason_code", 0),
        XShortField("inner_ether_type", 0),
    ]


class FabricHeader(Packet):
    name = "Fabric Header"
    fields_desc = [
        BitField("packet_type", 0, 3),
        BitField("header_version", 0, 2),
        BitField("packet_version", 0, 2),
        BitField("pad1", 0, 1),
        BitField("fabric_color", 0, 3),
        BitField("fabric_qos", 0, 5),
        XByteField("dst_device", 0),
        XShortField("dst_port_or_group", 0),
    ]


class FabricCpuHeader(Packet):
    name = "Fabric Cpu Header"
    fields_desc = [
        BitField("egress_queue", 0, 5), BitField("tx_bypass", 0, 1),
        BitField("reserved1", 0, 2), XShortField("ingress_port", 0),
        XShortField("ingress_ifindex", 0), XShortField("ingress_bd", 0),
        XShortField("reason_code", 0)
    ]


class FabricPayloadHeader(Packet):
    name = "Fabric Payload Header"
    fields_desc = [XShortField("ether_type", 0)]


class FabricCpuSflowHeader(Packet):
    name = "Fabric Cpu Sflow Header"
    fields_desc = [
        XShortField("sflow_sid", 0),
    ]


class FabricCpuBfdEventHeader(Packet):
    name = "Fabric Cpu BFD Event Header"
    fields_desc = [
        XShortField("bfd_sid", 0),
        XShortField("bfd_event", 0),
    ]


class VlanHeader(Packet):
    name = "vlan Header"

    fields_desc = [
        XShortField("ethtype", 0x8100),
        BitField("pcp", 0, 3), BitField("dei", 0, 1),
        BitField("vlanid", 0, 12),
        XShortField("tpid", 0),
    ]


def simple_cpu_vlan_packet(header_version=0,
                           packet_version=0,
                           fabric_color=0,
                           fabric_qos=0,
                           dst_device=0,
                           dst_port_or_group=0,
                           ingress_ifindex=0,
                           ingress_bd=0,
                           egress_queue=0,
                           tx_bypass=False,
                           ingress_port=1,
                           reason_code=0,
                           sflow_sid=0,
                           bfd_sid=0,
                           inner_pkt=None, vid=0):

    ether = Ether(str(inner_pkt))
    eth_type = ether.type
    ether.type = 0x9000

    fabric_header = FabricHeader(
        packet_type=0x5,
        header_version=header_version,
        packet_version=packet_version,
        pad1=0,
        fabric_color=fabric_color,
        fabric_qos=fabric_qos,
        dst_device=dst_device,
        dst_port_or_group=dst_port_or_group)

    fabric_cpu_header = FabricCpuHeader(
        egress_queue=egress_queue,
        tx_bypass=tx_bypass,
        reserved1=0,
        ingress_port=ingress_port,
        ingress_ifindex=ingress_ifindex,
        ingress_bd=ingress_bd,
        reason_code=reason_code)

    fabric_payload_header = FabricPayloadHeader(ether_type=eth_type)

    pkt = (str(ether)[:14]) / VlanHeader(vlanid=vid) / \
        fabric_header / fabric_cpu_header

    if sflow_sid:
        pkt = pkt / FabricCpuSflowHeader(sflow_sid=sflow_sid)
    elif bfd_sid:
        pkt = pkt / FabricCpuBfdEventHeader(bfd_sid=bfd_sid)

    pkt = pkt / fabric_payload_header

    if inner_pkt:
        pkt = pkt / (str(inner_pkt)[14:])
    else:
        ip_pkt = simple_ip_only_packet()
        pkt = pkt / ip_pkt

    return pkt


def simple_cpu_packet(header_version=0,
                      packet_version=0,
                      fabric_color=0,
                      fabric_qos=0,
                      dst_device=0,
                      dst_port_or_group=0,
                      ingress_ifindex=0,
                      ingress_bd=0,
                      egress_queue=0,
                      tx_bypass=False,
                      ingress_port=1,
                      reason_code=0,
                      sflow_sid=0,
                      bfd_sid=0,
                      inner_pkt=None):

    ether = Ether(str(inner_pkt))
    eth_type = ether.type
    ether.type = 0x9000

    fabric_header = FabricHeader(
        packet_type=0x5,
        header_version=header_version,
        packet_version=packet_version,
        pad1=0,
        fabric_color=fabric_color,
        fabric_qos=fabric_qos,
        dst_device=dst_device,
        dst_port_or_group=dst_port_or_group)

    fabric_cpu_header = FabricCpuHeader(
        egress_queue=egress_queue,
        tx_bypass=tx_bypass,
        reserved1=0,
        ingress_port=ingress_port,
        ingress_ifindex=ingress_ifindex,
        ingress_bd=ingress_bd,
        reason_code=reason_code)

    fabric_payload_header = FabricPayloadHeader(ether_type=eth_type)

    pkt = (str(ether)[:14]) / fabric_header / fabric_cpu_header

    if sflow_sid:
        pkt = pkt / FabricCpuSflowHeader(sflow_sid=sflow_sid)
    elif bfd_sid:
        pkt = pkt / FabricCpuBfdEventHeader(bfd_sid=bfd_sid)

    pkt = pkt / fabric_payload_header

    if inner_pkt:
        pkt = pkt / (str(inner_pkt)[14:])
    else:
        ip_pkt = simple_ip_only_packet()
        pkt = pkt / ip_pkt

    return pkt


def dumpObject(obj):
    for attr in dir(obj):
        if hasattr(obj, attr):
            print("obj.%s = %s" % (attr, getattr(obj, attr)))


def socket_verify_packet(pkt, s, timeout=2):
    MAX_PKT_SIZE = 9100
    timeout = time.time() + timeout
    while time.time() < timeout:
        try:
            packet_from_tap_device = Ether(s.recv(MAX_PKT_SIZE))
            # print "Expected packet"
            # print  ':'.join(x.encode('hex') for x in str(pkt))
            # print "packet_from_tap_device"
            # print  ':'.join(x.encode('hex') for x in str(packet_from_tap_device))
            if (str(packet_from_tap_device) == str(pkt)):
                return True
        except:
            pass
    return False


def socket_verify_pkt_str(pkt, s, timeout=2):
    MAX_PKT_SIZE = 9100
    timeout = time.time() + timeout
    while time.time() < timeout:
        try:
            packet_from_tap_device = Ether(s.recv(MAX_PKT_SIZE))
            # print "Expected packet"
            # print  ':'.join(x.encode('hex') for x in pkt)
            # print "packet_from_tap_device"
            # print  ':'.join(x.encode('hex') for x in str(packet_from_tap_device))
            if (str(packet_from_tap_device) == pkt):
                return True
        except:
            pass
    return False


def generate_filter(dest_mac=-1,
                    src_mac=-1,
                    outer_ether_type=-1,
                    packet_type=-1,
                    header_version=-1,
                    packet_version=-1,
                    pad1=-1,
                    fabric_color=-1,
                    fabric_qos=-1,
                    dst_device=-1,
                    dst_port_or_group=-1,
                    egress_queue=-1,
                    tx_bypass=-1,
                    reserved1=-1,
                    ingress_port=-1,
                    ingress_ifindex=-1,
                    ingress_bd=-1,
                    reason_code=-1,
                    inner_ether_type=-1):
    kfilter = Mask()
    kmask = Mask()
    if dest_mac != -1:
        kmask.dest_mac = 0xffffffffffff
        kfilter.dest_mac = dest_mac
    if src_mac != -1:
        kmask.src_mac = 0xffffffffffff
        kfilter.src_mac = src_mac
    if outer_ether_type != -1:
        kmask.outer_ether_type = 0xffff
        kfilter.outer_ether_type = outer_ether_type
    if packet_type != -1:
        kmask.packet_type = 7
        kfilter.packet_type = packet_type
    if header_version != -1:
        kmask.header_version = 3
        kfilter.header_version = header_version
    if packet_version != -1:
        kmask.packet_version = 3
        kfilter.packet_version = packet_version
    if pad1 != -1:
        kmask.pad1 = 1
        kfilter.pad1 = pad1
    if fabric_color != -1:
        kmask.fabric_color = 7
        kfilter.fabric_color = fabric_color
    if fabric_qos != -1:
        kmask.fabric_qos = 31
        kfilter.fabric_qos = fabric_qos
    if dst_device != -1:
        kmask.dst_device = 0xff
        kfilter.dst_device = dst_device
    if dst_port_or_group != -1:
        kmask.dst_port_or_group = 0xffff
        kfilter.dst_port_or_group = dst_port_or_group
    if egress_queue != -1:
        kmask.egress_queue = 31
        kfilter.egress_queue = egress_queue
    if tx_bypass != -1:
        kmask.tx_bypass = 1
        kfilter.tx_bypass = tx_bypass
    if reserved1 != -1:
        kmask.reserved1 = 3
        kfilter.reserved1 = reserved1
    if ingress_port != -1:
        kmask.ingress_port = 0xffff
        kfilter.ingress_port = ingress_port
    if ingress_ifindex != -1:
        kmask.ingress_ifindex = 0xffff
        kfilter.ingress_ifindex = ingress_ifindex
    if ingress_bd != -1:
        kmask.ingress_bd = 0xffff
        kfilter.ingress_bd = ingress_bd
    if reason_code != -1:
        kmask.reason_code = 0xffff
        kfilter.reason_code = reason_code
    if inner_ether_type != -1:
        kmask.inner_ether_type = 0xffff
        kfilter.inner_ether_type = inneer_ether_type

    return str(kfilter), str(kmask)


def open_packet_socket(hostif_name):
    s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
                      socket.htons(ETH_P_ALL))
    s.bind((hostif_name, ETH_P_ALL))
    s.setblocking(0)
    return s


def port_tbl_add(self, shdl, dev_tgt,
                 port, pad, fab_color,
                 fab_qos, dst_dev,
                 dst_port_or_grp,
                 reserved, ifindex,
                 bd, reason):

    match_spec = knet_mgr_test_port_tbl_match_spec_t(hex_to_i16(port))
    action_spec = knet_mgr_test_add_cpu_header_action_spec_t(pad, 0,
                                                             fab_qos, dst_dev,
                                                             dst_port_or_grp,
                                                             reserved, ifindex,
                                                             bd, reason)
    entry_hdl = self.client.port_tbl_table_add_with_add_cpu_header(
        shdl, dev_tgt, match_spec, action_spec)
    return entry_hdl


def verify_filter(ufilter, filter_res):
    if (ufilter.spec.priority != filter_res.spec.priority or ufilter.spec.filter_size != filter_res.spec.filter_size or ufilter.action.dest_type != filter_res.action.dest_type or ufilter.action.dest_proto != filter_res.action.dest_proto or ufilter.action.count != filter_res.action.count):
        return False

    for x in range(filter_res.spec.filter_size):
        if (ufilter.spec.filter[x] != filter_res.spec.filter[x] or ufilter.spec.mask[x] != filter_res.spec.mask[x]):
            return False

    for umut, rmut in zip(ufilter.action.pkt_mutation, filter_res.action.pkt_mutation):
        if(umut.mutation_type != rmut.mutation_type or umut.offset != rmut.offset or umut.len != rmut.len):
            return False
        if(umut.mutation_type == knet_mutation_type_t.BF_KNET_RX_MUT_INSERT):
            for x in range(rmut.len):
                if (umut.data[x] != rmut.data[x]):
                    return False
    return True

def verify_action(uaction, action_res):
    if(uaction.count != action_res.count):
         return False
    for umut, rmut in zip(uaction.pkt_mutation, action_res.pkt_mutation):
        if(umut.mutation_type != rmut.mutation_type or umut.offset != rmut.offset or umut.len != rmut.len):
            return False
        if(umut.mutation_type == knet_mutation_type_t.BF_KNET_RX_MUT_INSERT):
            for x in range(rmut.len):
                if (umut.data[x] != rmut.data[x]):
                    return False
    return True

def verify_list(plist,list_res):
    if set(plist) == set(list_res):
        return True
    return False

class TestKnet(pd_base_tests.ThriftInterfaceDataPlane):
    def __init__(self):
        pd_base_tests.ThriftInterfaceDataPlane.__init__(
            self, ["knet_mgr_test"])

    def baseApiTest(self):
        # Add veth251 as knet cpu port
        cpuif_ndev = self.knet_mgr.knet_cpuif_ndev_add("veth251")
#        cpuif_ndev = self.knet_mgr.knet_cpuif_ndev_add("veth64")
        self.assertTrue(cpuif_ndev.status == 0, )

        # Add test_hostif1 as hostif_knetdev
        hostif_ndev = self.knet_mgr.knet_hostif_kndev_add(
            cpuif_ndev.knet_cpuif_id, "test_hostif1")

        
        # Create Rx Filter to strip from offset 12,21 bytes
        filter1, mask1 = generate_filter(ingress_port=1)
        filter_spec = knet_rx_filter_spec_t(
            priority=1, filter=filter1, mask=mask1, filter_size=len(filter1))
        mutation_strip = knet_packet_mutation_t(
            mutation_type=knet_mutation_type_t.BF_KNET_RX_MUT_STRIP, offset=12, len=21)
        mutation_list1 = list()
        mutation_list1.append(mutation_strip)
        filter_action = knet_rx_filter_action_t(dest_type=knet_filter_dest_t.BF_KNET_FILTER_DESTINATION_HOSTIF,
                                                knet_hostif_id=hostif_ndev.knet_hostif_id, dest_proto=0, count=1, pkt_mutation=mutation_list1)
        rx_filter = knet_rx_filter_t(filter_spec, filter_action)
        filter_res = self.knet_mgr.knet_rx_filter_add(
            cpuif_ndev.knet_cpuif_id, rx_filter)

        # Add Tx action for test_hostif1
        fab_hdr = FabricHeader()
        cpu_hdr = FabricCpuHeader(ingress_port=1)
        hdr = str(fab_hdr / cpu_hdr)
        mutation_insert = knet_packet_mutation_t(
            mutation_type=knet_mutation_type_t.BF_KNET_RX_MUT_INSERT, data=hdr, offset=12, len=len(hdr))
        mutation_list2 = list()
        mutation_list2.append(mutation_insert)
        tx_action = knet_tx_action_t(count=1, pkt_mutation=mutation_list2)
        action_res = self.knet_mgr.knet_tx_action_add(
            cpuif_ndev.knet_cpuif_id, hostif_ndev.knet_hostif_id, tx_action)

        # Delete rx fitler
        self.knet_mgr.knet_rx_filter_delete(
            cpuif_ndev.knet_cpuif_id, filter_res.filter_id)

        # Delete tx action
        self.knet_mgr.knet_tx_action_delete(
            cpuif_ndev.knet_cpuif_id, hostif_ndev.knet_hostif_id)

        # Delete hostif
        self.knet_mgr.knet_hostif_kndev_delete(
            cpuif_ndev.knet_cpuif_id, hostif_ndev.knet_hostif_id)

        # Delete cpuif
        self.knet_mgr.knet_cpuif_ndev_delete(cpuif_ndev.knet_cpuif_id)

    def configureTbl(self, sess_hdl, dev_id, port_tbl_handles):

        default_action_spec = knet_mgr_test_add_cpu_header_action_spec_t(0, 0,
                                                                         0, 0,
                                                                         0, 0, 0,
                                                                         0, 0)
        dev_tgt = DevTarget_t(0, hex_to_i16(0xFFFF))
        port_tbl_hdl1 = self.client.port_tbl_set_default_action_add_cpu_header(
            sess_hdl, dev_tgt, default_action_spec)
        fab_tbl_hdl1 = self.client.fabric_tbl_set_default_action_set_egress_port(
            sess_hdl, dev_tgt)

        # For packet coming on port 0 set pad to 1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 1 set fab_qos to  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 2 set dst_dev to  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 3 set dst_port_or_grp to  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 4 set reserved to  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 4, 0, 0, 0, 0, 1, 0, 0, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 5 set ifindex as  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 6 set ifindex as  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 6, 0, 0, 0, 0, 0, 0, 1, 0, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 7 set bd as  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 128, 0, 0, 0, 0, 0, 0, 0, 1, 0)
        port_tbl_handles.append(entry_hdl)

        # For packet coming on port 8 set reasonCode as  1
        entry_hdl = port_tbl_add(
            self, sess_hdl, dev_tgt, 144, 0, 0, 0, 0, 0, 0, 0, 0, 1)
        port_tbl_handles.append(entry_hdl)

    def runTest(self):
        cpuif_ndev = self.knet_mgr.knet_cpuif_ndev_add("pcie0")
#        cpuif_ndev = self.knet_mgr.knet_cpuif_ndev_add("veth64")
        self.assertTrue(cpuif_ndev.status == 0, )
        print("cpuif_ndev.knet_cpuif_id:", cpuif_ndev.knet_cpuif_id)
        # Add test_hostif1 as hostif_knetdev
        hostif_ndev = self.knet_mgr.knet_hostif_kndev_add(
            cpuif_ndev.knet_cpuif_id, "test_hostif1")
